<!DOCTYPE html>


<html lang="en">
  

    <head>
      <meta charset="utf-8" />
        
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>Java JPA学习 |  麦田麦穗</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    </head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-java/Java-JPA思考"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  Java JPA学习
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2020/09/23/java/Java-JPA%E6%80%9D%E8%80%83/" class="article-date">
  <time datetime="2020-09-23T08:56:11.000Z" itemprop="datePublished">2020-09-23</time>
</a>   
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">3.3k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">13 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p><img src="http://img.rc5j.cn/blog20200923170827.png" alt=""></p>
<p>JPA即Java Persistence API. 2006年5月11号，JPA 1.0 规范作为 JCP JSR 220 的一部分最终被发布。</p>
<p>在PHP世界当中doctrine、cake ORM 都有JPA的影子。</p>
<span id="more"></span>

<h2 id="Entity"><a href="#Entity" class="headerlink" title="Entity"></a>Entity</h2><p>持久化实体是一个轻量级的 Java 类，其状态通常持久地保存到关系数据库的表中。 这种实体的实例对应于表中的各个行。 实体之间通常有关系，这些关系通过对象/关系元数据表示。 可以在实体类文件中直接使用注释来指定这种关系，也可以在随应用程序分发的单独XML描述文件中指定。</p>
<h2 id="JPQL"><a href="#JPQL" class="headerlink" title="JPQL"></a>JPQL</h2><p>Java持久化查询语言 （JPQL）对存储在关系数据库中的实体进行查询。查询在语法上类似于SQL查询，但是操作的是实体对象而不是直接对数据库表进行操作。</p>
<h2 id="动机"><a href="#动机" class="headerlink" title="动机"></a>动机</h2><p>在引入EJB 3.0规范之前，许多企业级Java开发人员使用由持久化框架（例如Hibernate）或数据访问对象（DAO）提供的轻量级持久化对象，来代替实体bean（EJB的一种）。 这是因为在以前的EJB规范中，实体bean需要太多复杂的代码和繁重的资源占用，并且由于bean和DAO对象或持久化框架之间的源代码中的互连和依赖性，它们只能在Java EE应用程序服务器中使用。 因此，最初在第三方持久性框架中提供的许多功能都被合并到Java Persistence API中，并且从2006年开始，<code>像Hibernate（版本3.2）和TopLink Essentials这样的项目已经实现Java Persistence API规范。</code></p>
<h2 id="JPA提供商"><a href="#JPA提供商" class="headerlink" title="JPA提供商"></a>JPA提供商</h2><p>JPA是一个开源API，因此Oracle，Redhat，Eclipse等各种企业供应商通过在其中添加JPA持久性风格来提供新产品。 其中一些产品包括:</p>
<p>Hibernate, Eclipselink, Toplink, Spring Data JPA, etc.</p>
<p>JSR定义了标准，众多组织对这个标准进行了实现，这使得开发者几乎可以在不同的实现版本里无缝切换。</p>
<h2 id="spring-data-jpa"><a href="#spring-data-jpa" class="headerlink" title="spring-data-jpa"></a>spring-data-jpa</h2><p>事实上Spring-data-jpa并不是jpa的具体实现或提供商。它只是jpa的一个数据访问抽象.在spring-data-jpa中你可以使用<br>Hibernate, Eclipse Link, 和其他的JPA provider。</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211222145130.png" alt=""></p>
<h3 id="hibernate"><a href="#hibernate" class="headerlink" title="hibernate"></a>hibernate</h3><p>spring-data-jpa 默认使用的是hibernate,我们可以查看使用的版本。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Application</span> <span class="token punctuation">&#123;</span>

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">void</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">[</span><span class="token punctuation">]</span> args<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">SpringApplication</span><span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token class-name">Application</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">;</span>
        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"spring version is : %s"</span><span class="token punctuation">,</span><span class="token class-name">SpringVersion</span><span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">//查看 hibernate 版本</span>
        log<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"hibernate version is : %s"</span><span class="token punctuation">,</span><span class="token class-name">Version</span><span class="token punctuation">.</span><span class="token function">getVersionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>

<span class="token punctuation">&#125;</span></code></pre>

<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211224111236.png" alt=""></p>
<p>更多官方文档可查阅 <a target="_blank" rel="noopener" href="https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html">https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html</a></p>
<h2 id="注解-Annotations"><a href="#注解-Annotations" class="headerlink" title="注解 Annotations"></a>注解 Annotations</h2><p>通常，Xml文件用于配置特定组件，或映射两种不同规格的组件。 在我们的例子中，我们必须在框架中单独维护xml。 这意味着在编写映射xml文件时，我们需要将POJO类属性与mapping.xml文件中的实体标记进行比较。</p>
<p>这是解决方案:在类定义中，我们可以使用注释编写配置部分。 注释用于类，属性和方法。 注释以“@”符号开头。 在声明类，属性或方法之前声明注释。 JPA的所有注释都在javax.persistence包中定义。</p>
<p>以下是我们的示例中使用的注释列表</p>
<p>注解 描述</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>@Entity</td>
<td>此批注指定将类声明为实体或表</td>
</tr>
<tr>
<td>@Table</td>
<td>此批注指定声明表名。</td>
</tr>
<tr>
<td>@Basic</td>
<td>此批注明确指定非约束字段</td>
</tr>
<tr>
<td>@Embedded</td>
<td>此批注指定类或实体的属性，该实体的可嵌入类的值实例。</td>
</tr>
<tr>
<td>@Id</td>
<td>此批注指定属性，用于类的标识（表的主键）。</td>
</tr>
<tr>
<td>@GeneratedValue</td>
<td>此批注指定了如何初始化标识属性，例如自动，手动或从序列表中获取的值。</td>
</tr>
<tr>
<td>@Transient</td>
<td>此批注指定了不持久的属性，即该值永远不会存储到数据库中。</td>
</tr>
<tr>
<td>@Column</td>
<td>此批注用于指定持久性属性的列或属性。</td>
</tr>
<tr>
<td>@SequenceGenerator</td>
<td>此批注用于定义@GeneratedValue批注中指定的属性的值。 它创建了一个序列。</td>
</tr>
<tr>
<td>@TableGenerator</td>
<td>此批注用于指定@GeneratedValue批注中指定的属性的值生成器。 它创建了一个价值生成表。</td>
</tr>
<tr>
<td>@AccessType</td>
<td>此类注释用于设置访问类型。 如果设置@AccessType（FIELD），则会发生字段访问。 如果设置@AccessType（PROPERTY），则将进行Property wise评估。</td>
</tr>
<tr>
<td>@JoinColumn</td>
<td>此批注用于指定实体关联或实体集合。 这用于多对一和一对多关联。</td>
</tr>
<tr>
<td>@UniqueConstraint</td>
<td>此批注用于指定主要或辅助表的字段，唯一约束。</td>
</tr>
<tr>
<td>@ColumnResult</td>
<td>此批注使用select子句引用SQL查询中的列的名称。</td>
</tr>
<tr>
<td>@ManyToMany</td>
<td>此批注用于定义连接表之间的多对多关系。</td>
</tr>
<tr>
<td>@ManyToOne</td>
<td>此批注用于定义连接表之间的多对一关系。</td>
</tr>
<tr>
<td>@OneToMany</td>
<td>此批注用于定义连接表之间的一对多关系。</td>
</tr>
<tr>
<td>@OneToOne</td>
<td>此批注用于定义连接表之间的一对一关系。</td>
</tr>
<tr>
<td>@NamedQueries</td>
<td>此批注用于指定命名查询的列表。</td>
</tr>
<tr>
<td>@NamedQuery</td>
<td>此批注用于使用静态名称指定查询。</td>
</tr>
</tbody></table>
<h3 id="Transient"><a href="#Transient" class="headerlink" title="Transient"></a>Transient</h3><blockquote>
<p>此批注指定了不持久的属性，即该值永远不会存储到数据库中。</p>
</blockquote>
<p>JPA默认所有Entity属性都是数据表Column,如果需要增加其他衍生属性而不需要成为数据表字段，则需要此注解。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token keyword">private</span> <span class="token class-name">Integer</span> purchasingPriceFen<span class="token punctuation">;</span>
<span class="token annotation punctuation">@Transient</span>
<span class="token keyword">private</span> <span class="token class-name">String</span> purchasingPrice<span class="token punctuation">;</span>
<span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getPurchasingPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    <span class="token keyword">return</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"%d"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>purchasingPriceFen <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<p>本例中，<code>purchasingPrice</code>是<code>purchasingPriceFen</code>的格式化表示，方便一些实体属性的格式化输出。</p>
<h3 id="Table"><a href="#Table" class="headerlink" title="Table"></a>Table</h3><p>多为数据库表名与entity类名不一致的情况.</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Entity</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>

<span class="token punctuation">&#125;</span></code></pre>

<p>在实际开发当中,我们都希望代码注释和数据表注释越全越好。那么如何使用JPA定义表的注释呢。<code>javax.persistence.Table</code>本身办不到，但是其实现方hibernate的Table注解可以。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token keyword">import</span> <span class="token namespace">org<span class="token punctuation">.</span>hibernate<span class="token punctuation">.</span>annotations<span class="token punctuation">.</span></span><span class="token class-name">Table</span><span class="token punctuation">;</span>
<span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>appliesTo <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">,</span> comment <span class="token operator">=</span> <span class="token string">"订单表"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>

<span class="token punctuation">&#125;</span></code></pre>

<h3 id="Column"><a href="#Column" class="headerlink" title="Column"></a>Column</h3><p>该注解用于定义字段,对应到数据库表的DDL。该字段我建议多使用columnDefinition定义好字段的数据类型,comment</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>columnDefinition <span class="token operator">=</span> <span class="token string">"varchar(25) NOT NULL default '无名' comment '商品名'"</span><span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token class-name">String</span> name<span class="token punctuation">;</span></code></pre>

<h3 id="Temporal"><a href="#Temporal" class="headerlink" title="Temporal"></a>Temporal</h3><p>@Temporal 是属性或方法级别的注解，用于声明属性持久化到数据库时所使用的时间精度。该注解可以应用于任何以下类型的实体类属性：</p>
<p>java.util.Date<br>java.util.Calendar</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Temporal</span><span class="token punctuation">(</span><span class="token class-name">TemporalType</span><span class="token punctuation">.</span>TIME<span class="token punctuation">)</span>
 <span class="token keyword">private</span> <span class="token class-name">Date</span> tokenExpiredTime<span class="token punctuation">;</span>
</code></pre>

<h2 id="Entity-Relationships"><a href="#Entity-Relationships" class="headerlink" title="Entity Relationships"></a>Entity Relationships</h2><p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223101826.png" alt=""></p>
<p>实体关系，RDBMS与NoSQL最直接的区别,直接影响到了我们应用程序的业务数据建模。那么JPA是如何来表示实体关系呢。</p>
<h3 id="一对一"><a href="#一对一" class="headerlink" title="一对一"></a>一对一</h3><p>关系A和关系B是一对一的关系，一般在一个表里有另一个表的关联id,进行关联。</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223102537.png" alt=""></p>
<p>例如,用户与当前家庭住址是一对一关系。商品与商品类别是一对一关系。</p>
<p>我们先按官方指导来使用。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token comment">//定义地址实体</span>
<span class="token annotation punctuation">@Entity</span>
<span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>appliesTo <span class="token operator">=</span> <span class="token string">"address"</span><span class="token punctuation">,</span> comment <span class="token operator">=</span> <span class="token string">"地址表"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Address</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>
    <span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>columnDefinition <span class="token operator">=</span> <span class="token string">"varchar(50) NOT NULL COMMENT '详细地址'"</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> detail<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token class-name">String</span> <span class="token function">getDetail</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">return</span> detail<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">setDetail</span><span class="token punctuation">(</span><span class="token class-name">String</span> detail<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>detail <span class="token operator">=</span> detail<span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
<span class="token punctuation">&#125;</span></code></pre>

<pre class="language-java" data-language="java"><code class="language-java"><span class="token comment">//定义用户实体</span>
<span class="token annotation punctuation">@Entity</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>

    <span class="token annotation punctuation">@NotBlank</span><span class="token punctuation">(</span>message <span class="token operator">=</span> <span class="token string">"名字不可为空"</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">String</span> name<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token keyword">short</span> age<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token class-name">String</span> mobile<span class="token punctuation">;</span>
    
    <span class="token annotation punctuation">@OneToOne</span>
    <span class="token annotation punctuation">@JoinColumn</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"address_id"</span><span class="token punctuation">,</span> referencedColumnName <span class="token operator">=</span> <span class="token string">"id"</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Address</span> address<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<p><code>JoinColumn</code>注解定义了User表的关联字段和关联表address的关联字段。对应到数据库里会自动生成外键并行成外键约束。</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223135649.png" alt=""></p>
<p>再来看看当表被关联了如何做CURD。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@RestController</span>
<span class="token annotation punctuation">@Log4j2</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserController</span> <span class="token punctuation">&#123;</span>

    <span class="token annotation punctuation">@Autowired</span>
    <span class="token class-name">UserService</span> userService<span class="token punctuation">;</span>

    <span class="token annotation punctuation">@Autowired</span>
    <span class="token keyword">private</span> <span class="token class-name">UserRepository</span> userRepository<span class="token punctuation">;</span>

    <span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"用户添加"</span><span class="token punctuation">,</span> path <span class="token operator">=</span> <span class="token string">"/user/add"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> <span class="token class-name">Response</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Valid</span> <span class="token annotation punctuation">@RequestBody</span> <span class="token class-name">User</span> user<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">Optional</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">User</span><span class="token punctuation">></span></span> user1 <span class="token operator">=</span> userRepository<span class="token punctuation">.</span><span class="token function">findByName</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        userRepository<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token string">"获取成功"</span><span class="token punctuation">,</span> user<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>

    <span class="token annotation punctuation">@GetMapping</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"用户详情"</span><span class="token punctuation">,</span>path <span class="token operator">=</span> <span class="token string">"/user/&#123;id&#125;"</span><span class="token punctuation">)</span>
    <span class="token keyword">public</span> <span class="token class-name">Response</span> <span class="token function">detail</span><span class="token punctuation">(</span><span class="token annotation punctuation">@PathVariable</span> <span class="token class-name">Long</span> id<span class="token punctuation">)</span><span class="token punctuation">&#123;</span>
        <span class="token class-name">Optional</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">User</span><span class="token punctuation">></span></span> user <span class="token operator">=</span> userRepository<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">isPresent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
            <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token class-name">ResponseCode</span><span class="token punctuation">.</span>success<span class="token punctuation">,</span> <span class="token string">"获取成功"</span><span class="token punctuation">,</span> user<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">&#125;</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token class-name">ResponseCode</span><span class="token punctuation">.</span>noDataFound<span class="token punctuation">,</span> <span class="token string">"获取失败"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>

<span class="token punctuation">&#125;</span></code></pre>

<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223135833.png" alt=""></p>
<p>在数据提交时，关联表的数据用嵌套的json进行提交即可。数据查询时使用<code>Repository</code>会自动进行关联查询,查看日志可以看到实际的sql.</p>
<pre class="language-sql" data-language="sql"><code class="language-sql"><span class="token keyword">SELECT</span>
    user0_<span class="token punctuation">.</span>id <span class="token keyword">AS</span> id1_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>create_time <span class="token keyword">AS</span> create_t2_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>modify_time <span class="token keyword">AS</span> modify_t3_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>address_id <span class="token keyword">AS</span> address_7_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>age <span class="token keyword">AS</span> age4_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>mobile <span class="token keyword">AS</span> mobile5_4_0_<span class="token punctuation">,</span>
    user0_<span class="token punctuation">.</span>name <span class="token keyword">AS</span> name6_4_0_<span class="token punctuation">,</span>
    address1_<span class="token punctuation">.</span>id <span class="token keyword">AS</span> id1_0_1_<span class="token punctuation">,</span>
    address1_<span class="token punctuation">.</span>create_time <span class="token keyword">AS</span> create_t2_0_1_<span class="token punctuation">,</span>
    address1_<span class="token punctuation">.</span>modify_time <span class="token keyword">AS</span> modify_t3_0_1_<span class="token punctuation">,</span>
    address1_<span class="token punctuation">.</span>detail <span class="token keyword">AS</span> detail4_0_1_
<span class="token keyword">FROM</span>
    <span class="token keyword">USER</span> user0_
    <span class="token keyword">LEFT</span> <span class="token keyword">OUTER</span> <span class="token keyword">JOIN</span> address address1_ <span class="token keyword">ON</span> user0_<span class="token punctuation">.</span>address_id <span class="token operator">=</span> address1_<span class="token punctuation">.</span>id
<span class="token keyword">WHERE</span>
    user0_<span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token number">3</span></code></pre>

<p><strong>这点在某种程度上会让开发变得很方便，但也会有潜在的性能风险，因为多表关联数据量大的情况下必然会使得性能下降</strong></p>
<p>查询结果也以嵌套json对象的形式展现这点非常棒。</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223140828.png" alt=""></p>
<h3 id="javax-persistence-CascadeType"><a href="#javax-persistence-CascadeType" class="headerlink" title="javax.persistence.CascadeType"></a>javax.persistence.CascadeType</h3><p>在定义关联关系时可以定义,级联的关系操作。</p>
<table>
<thead>
<tr>
<th>类型</th>
<th>说明</th>
<th>解释</th>
</tr>
</thead>
<tbody><tr>
<td>ALL</td>
<td>级联所有实体状态转换</td>
<td>拥有所有级联操作权限。</td>
</tr>
<tr>
<td>PERSIST</td>
<td>级联实体持久化操作</td>
<td>当父实体被持久化时，会连同持久化子实体</td>
</tr>
<tr>
<td>MERGE</td>
<td>级联实体合并操作</td>
<td>当Student中的数据改变，会相应地更新Course中的数据。</td>
</tr>
<tr>
<td>REMOVE</td>
<td>级联实体删除操作</td>
<td>删除当前实体时，与它有映射关系的实体也会跟着被删除。</td>
</tr>
<tr>
<td>REFRESH</td>
<td>级联实体刷新操作</td>
<td>假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。</td>
</tr>
<tr>
<td>DETACH</td>
<td>级联实体分离操作</td>
<td></td>
</tr>
</tbody></table>
<h3 id="FetchType"><a href="#FetchType" class="headerlink" title="FetchType"></a>FetchType</h3><p>在定义实体关系的<code>OneToOne</code>或其他关系注解时，我们可以定义fetch属性。</p>
<ul>
<li>FetchType.LAZY</li>
<li>FetchType.EAGER</li>
</ul>
<p>lazy即懒惰模式eager渴望模式。表现差异在于eager会一开始就查找出关联实体，而lazy模式是当你调用相应关联get方法时才会查询。本质区别在于lazy是分sql查询,eager是join关联查询。</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223144919.png" alt=""></p>
<p>当我们定义<code>@OneToOne(fetch = FetchType.LAZY)</code>查看日志的话，我们可以看到之前的join变成了2个select。</p>
<p>不过会存在一个问题是在被指定为lazy的属性对象里返回的json会有个<code>hibernateLazyInitializer</code>,如果你不想展示的话可以使用注解<code>JsonIgnoreProperties</code>定义到该对象类</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@JsonIgnoreProperties</span><span class="token punctuation">(</span>value<span class="token operator">=</span><span class="token punctuation">&#123;</span><span class="token string">"hibernateLazyInitializer"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Address</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>
<span class="token punctuation">&#125;</span></code></pre>

<h3 id="思考"><a href="#思考" class="headerlink" title="思考"></a>思考</h3><p>当在大型应用当中,我建议不要进行外键约束和不要使用EAGER。因为关联查询往往会导致慢查询而拖垮数据库，而1次的sql和2-3次的sql其实在接口层面差异并不明显。</p>
<h2 id="ManyToMany"><a href="#ManyToMany" class="headerlink" title="ManyToMany"></a>ManyToMany</h2><p>在许多场景当中，关系都是多对多的对应关系。拿电商业务来说，订单与商品的关系是多对多的关系。即1个订单可以有多个商品，1个商品可以在多个订单当中。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Entity</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>appliesTo <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">,</span> comment <span class="token operator">=</span> <span class="token string">"订单表"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>

    <span class="token keyword">private</span> <span class="token class-name">String</span> orderNo<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token class-name">Long</span> uid<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token class-name">Integer</span> status<span class="token punctuation">;</span>

    <span class="token keyword">private</span> <span class="token class-name">Integer</span> orderPriceFen<span class="token punctuation">;</span>

    <span class="token annotation punctuation">@ManyToMany</span><span class="token punctuation">(</span>fetch <span class="token operator">=</span> <span class="token class-name">FetchType</span><span class="token punctuation">.</span>LAZY<span class="token punctuation">)</span>
    <span class="token annotation punctuation">@JoinTable</span><span class="token punctuation">(</span>foreignKey <span class="token operator">=</span> <span class="token annotation punctuation">@ForeignKey</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">ConstraintMode</span><span class="token punctuation">.</span>NO_CONSTRAINT<span class="token punctuation">)</span><span class="token punctuation">,</span>
            inverseForeignKey <span class="token operator">=</span> <span class="token annotation punctuation">@ForeignKey</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">ConstraintMode</span><span class="token punctuation">.</span>NO_CONSTRAINT<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Goods</span><span class="token punctuation">></span></span> goods<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<ul>
<li>我们在订单表中定义了goods属性并用<code>ManyToMany</code>表示是多对多的关系。</li>
<li>使用<code>ConstraintMode.NO_CONSTRAINT</code>去除外键约束。</li>
<li>jpa会生成关联表</li>
</ul>
<pre class="language-java" data-language="java"><code class="language-java">
<span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">"/order/create"</span><span class="token punctuation">,</span> name <span class="token operator">=</span> <span class="token string">"订单创建"</span><span class="token punctuation">,</span> method <span class="token operator">=</span> <span class="token class-name">RequestMethod</span><span class="token punctuation">.</span>POST<span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token class-name">Response</span> <span class="token function">createOrder</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Valid</span> <span class="token annotation punctuation">@RequestBody</span> <span class="token class-name">Order</span> order<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
    order<span class="token punctuation">.</span><span class="token function">setOrderNo</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"%d"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    order<span class="token punctuation">.</span><span class="token function">setUid</span><span class="token punctuation">(</span><span class="token number">3L</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Goods</span><span class="token punctuation">></span></span> orderGoods <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token class-name">Goods</span> g <span class="token operator">:</span> order<span class="token punctuation">.</span><span class="token function">getGoods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
        <span class="token class-name">Optional</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Goods</span><span class="token punctuation">></span></span> goods <span class="token operator">=</span> goodsRepository<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>g<span class="token punctuation">.</span><span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        goods<span class="token punctuation">.</span><span class="token function">ifPresent</span><span class="token punctuation">(</span>orderGoods<span class="token operator">::</span><span class="token function">add</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">&#125;</span>
    order<span class="token punctuation">.</span><span class="token function">setGoods</span><span class="token punctuation">(</span>orderGoods<span class="token punctuation">)</span><span class="token punctuation">;</span>
    orderRepository<span class="token punctuation">.</span><span class="token function">save</span><span class="token punctuation">(</span>order<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token class-name">ResponseCode</span><span class="token punctuation">.</span>success<span class="token punctuation">,</span> <span class="token string">"创建成功"</span><span class="token punctuation">,</span> order<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223190914.png" alt=""></p>
<h3 id="Many-to-Many-With-a-New-Entity"><a href="#Many-to-Many-With-a-New-Entity" class="headerlink" title="Many-to-Many With a New Entity"></a>Many-to-Many With a New Entity</h3><p>有种场景是多对多的关联表还需要其他属性，可能这是实际当中更为常见的情况。比如订单商品表还需要记录商品数量,这个时候最好的方式是用一个新的Entity来建立多对多的关联关系。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Entity</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderGoods</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>

    <span class="token annotation punctuation">@Column</span><span class="token punctuation">(</span>columnDefinition <span class="token operator">=</span> <span class="token string">"int NOT NULL comment '数量'"</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token keyword">int</span> nums<span class="token punctuation">;</span>

    <span class="token annotation punctuation">@ManyToOne</span>
    <span class="token annotation punctuation">@JoinColumn</span><span class="token punctuation">(</span>foreignKey <span class="token operator">=</span> <span class="token annotation punctuation">@ForeignKey</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">ConstraintMode</span><span class="token punctuation">.</span>NO_CONSTRAINT<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token annotation punctuation">@JsonIgnoreProperties</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token string">"orderGoods"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Order</span> order<span class="token punctuation">;</span>

    <span class="token annotation punctuation">@ManyToOne</span>
    <span class="token annotation punctuation">@JoinColumn</span><span class="token punctuation">(</span>foreignKey <span class="token operator">=</span> <span class="token annotation punctuation">@ForeignKey</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token class-name">ConstraintMode</span><span class="token punctuation">.</span>NO_CONSTRAINT<span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Goods</span> goods<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<p>还需对<code>Goods</code>和<code>Order</code>进行<code>mappedBy</code>,防止被重复关联。</p>
<pre class="language-java" data-language="java"><code class="language-java"><span class="token annotation punctuation">@Entity</span>
<span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>appliesTo <span class="token operator">=</span> <span class="token string">"goods"</span><span class="token punctuation">,</span> comment <span class="token operator">=</span> <span class="token string">"商品表"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Goods</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>
   <span class="token annotation punctuation">@OneToMany</span><span class="token punctuation">(</span>mappedBy <span class="token operator">=</span> <span class="token string">"goods"</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">OrderGoods</span><span class="token punctuation">></span></span> orderGoods<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span>

<span class="token annotation punctuation">@Entity</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">)</span>
<span class="token annotation punctuation">@Table</span><span class="token punctuation">(</span>appliesTo <span class="token operator">=</span> <span class="token string">"dorder"</span><span class="token punctuation">,</span> comment <span class="token operator">=</span> <span class="token string">"订单表"</span><span class="token punctuation">)</span>
<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span> <span class="token keyword">extends</span> <span class="token class-name">BaseEntity</span> <span class="token punctuation">&#123;</span>
    <span class="token annotation punctuation">@OneToMany</span><span class="token punctuation">(</span>mappedBy <span class="token operator">=</span> <span class="token string">"order"</span><span class="token punctuation">)</span>
    <span class="token annotation punctuation">@Transient</span>
    <span class="token annotation punctuation">@JsonIgnoreProperties</span><span class="token punctuation">(</span>value <span class="token operator">=</span> <span class="token punctuation">&#123;</span><span class="token string">"order"</span><span class="token punctuation">&#125;</span><span class="token punctuation">)</span>
    <span class="token keyword">private</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">OrderGoods</span><span class="token punctuation">></span></span> orderGoods<span class="token punctuation">;</span>
<span class="token punctuation">&#125;</span></code></pre>

<p>最终完成订单和订单商品的添加</p>
<p><img src="https://vison-blog.oss-cn-beijing.aliyuncs.com/20211223232404.png" alt=""></p>
<h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><p>以上代码与直接的<code>ManyToMany</code>方式不同的是，我没能实现主表的save让JPA自动将关联表数据也插入,是我手动处理的。<br>因为遇到的问题现在还没解决。</p>
<p>想让其自动将关联数据save,把hibernate的info日志打开。会发现<code>Collection found: was: [&lt;unreferenced&gt;] (initialized)</code>日志记录,应该是哪里不对，但是目前不纠结了。</p>
<p>以上代码虽然能完成需求但是数据插入并不是在一个事务内执行的，因此实际生产当中这种写法也不可取,需写在一个事务内。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a target="_blank" rel="noopener" href="https://fanlychie.github.io/post/jpa-column-annotation.html">https://fanlychie.github.io/post/jpa-column-annotation.html</a></li>
<li><a target="_blank" rel="noopener" href="https://dzone.com/articles/what-is-the-difference-between-hibernate-and-sprin-1">https://dzone.com/articles/what-is-the-difference-between-hibernate-and-sprin-1</a></li>
</ol>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          Donate
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>Copyright： </strong>
          
          Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://visonforcoding.xyz/2020/09/23/java/Java-JPA%E6%80%9D%E8%80%83/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Java/" rel="tag">Java</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/09/24/java/Introduction-to-Spring-MVC-HandlerInterceptor%E6%8B%A6%E6%88%AA%E5%99%A8%E4%BB%8B%E7%BB%8D/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            Introduction to Spring MVC HandlerInterceptor拦截器介绍
          
        </div>
      </a>
    
    
      <a href="/2020/09/17/php/PHP-QA%E4%B9%8BPHPStan/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">PHP QA</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2022
        <i class="ri-heart-fill heart_icon"></i> vison
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="麦田麦穗"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E6%97%85%E8%A1%8C/">旅行</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" target="_blank" rel="noopener" href="http://shenyu-vip.lofter.com">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/null">Gallery</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>Buy me a cup of coffee~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpeg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wallet.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>